﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mystery.SCNR.Scripts;
using H2.Streams;
using System.Windows.Forms;

namespace Mystery.SCNR
{
    public class Script
    {
        public static Script Current { get; private set; }

        public ScriptFunction[] Functions;
        public ScriptGlobal[] Globals;
        public ScriptingData Data;
        public ScriptObject[] Objects;
        public ScriptingSyntaxDatum[] SyntaxDatums;
        public ScriptStringData StringData;

        public static Script Read(MapStream map, int scnrOffset)
        {
            Script s = new Script();
            s.Functions = ScriptFunction.Read(map, scnrOffset);
            s.Globals = ScriptGlobal.Read(map, scnrOffset);
            s.Data = ScriptingData.Read(map, scnrOffset);
            s.Objects = ScriptObject.Read(map, scnrOffset);
            s.SyntaxDatums = ScriptingSyntaxDatum.Read(map, scnrOffset);
            s.StringData = ScriptStringData.Read(map, scnrOffset);

            Current = s;

            return s;
        }

        public string GetString(uint startOffset)
        { return StringData.GetName(startOffset); }

        public string GetObjectName(uint objectIndex)
        { return Objects[objectIndex].Name; }

        public string GetGlobalName(uint globalIndex)
        { return Globals[globalIndex].Name; }

        public string GetFunctionName(uint functionIndex)
        { return Functions[functionIndex].GetName(); }

        public ScriptingSyntaxDatum[] GetFunctionDatums(uint functionIndex)
        {
            List<ScriptingSyntaxDatum> functionDatums = new List<ScriptingSyntaxDatum>();

            Add(SyntaxDatums[Functions[functionIndex].SyntaxChunkNumber], functionDatums);

            return functionDatums.ToArray();
        }

        private void Add(ScriptingSyntaxDatum datum, List<ScriptingSyntaxDatum> datumList)
        {
            datumList.Add(datum);

            if (datum.HasChild())
                Add(datum.GetChild(SyntaxDatums), datumList);

            if (datum.HasSibling())
                Add(datum.GetSibling(SyntaxDatums), datumList);
        }

        public string GetFunctionCode(ScriptFunction function)
        {
            return GetDatumCode(SyntaxDatums[function.SyntaxChunkNumber], null);
        }

        public string GetFunctionCode(int functionIndex)
        {
            return GetDatumCode(SyntaxDatums[Functions[functionIndex].SyntaxChunkNumber], null);
        }

        private string GetDatumCode(ScriptingSyntaxDatum datum, ScriptingSyntaxDatum parent)
        {
            string childCode = string.Empty;
            string siblingCode = string.Empty;

            if (datum.HasChild())
                childCode = GetDatumCode(datum.Child, datum);

            if (datum.HasSibling())
                siblingCode = GetDatumCode(datum.Sibling, parent);

            StringBuilder builder = new StringBuilder(datum.GetStartString());

            builder.Append(childCode);

            builder.Append(datum.GetEndString());

            if (datum.Previous != null && datum.Previous != parent && datum.HasSibling())
                builder.Append(parent.GetSeparatorString());

            builder.Append(siblingCode);
            
            return builder.ToString();
        }

        public List<ScriptFunction> FindAllFunctions(string text)
        {
            List<ScriptFunction> found = new List<ScriptFunction>();
            foreach (ScriptFunction f in Functions)
                if (f.Name.StartsWith(text)) found.Add(f);
            return found;
        }

        public bool HasReference(ScriptingSyntaxDatum datum, string text)
        {
            if (datum.GetStartString().StartsWith(text))
                return true;

            if (datum.HasChild() && HasReference(datum.GetChild(SyntaxDatums), text))
                return true;

            if (datum.HasSibling() && HasReference(datum.GetSibling(SyntaxDatums), text))
                return true;

            return false;
        }

        public List<ScriptFunction> FindAllReferences(BuiltInFunctions builtInFunction)
        {
            List<ScriptFunction> found = new List<ScriptFunction>();
            foreach (ScriptFunction f in Functions)
                if (HasReference(SyntaxDatums[f.SyntaxChunkNumber], builtInFunction)) found.Add(f);
            return found;
        }

        public bool HasReference(ScriptingSyntaxDatum datum, BuiltInFunctions builtInFunction)
        {
            if (datum.OwnerFunction == builtInFunction)
                return true;

            if (datum.HasChild() && HasReference(datum.GetChild(SyntaxDatums), builtInFunction))
                return true;

            if (datum.HasSibling() && HasReference(datum.GetSibling(SyntaxDatums), builtInFunction))
                return true;

            return false;
        }

        //public void RemoveAllReferences(ScriptFunction script)
        //{
        //    foreach (ScriptFunction f in Functions)
        //        RemoveReferences(SyntaxDatums[f.SyntaxChunkNumber], script.GetName() + '(');
        //}

        //private void RemoveReferences(ScriptingSyntaxDatum datum, string scriptName)
        //{
        //    if (datum.HasChild())
        //        RemoveReferences(datum.GetChild(SyntaxDatums), scriptName);

        //    if (datum.HasSibling())
        //        RemoveReferences(datum.GetSibling(SyntaxDatums), scriptName);

        //    if (datum.Type == VariableTypes.FunctionName)
        //    {
        //        string value = (string)datum.GetValue();
        //        if (datum.GetValue().Equals(scriptName))
        //        {
        //            //Relink
        //            ScriptingSyntaxDatum topParent = datum;
        //            do topParent = topParent.Previous;
        //            while (topParent.Previous.OwnerFunction != BuiltInFunctions.Begin);
                        
        //            topParent.Previous.SetSibling(SyntaxDatums, topParent.SiblingIndex);
        //        }
        //    }
        //}

        //public void NullFunction(ScriptFunction script)
        //{
        //    ScriptingSyntaxDatum first = SyntaxDatums[script.SyntaxChunkNumber].Child.Sibling;
        //    //script.SyntaxChunkNumber = -1;
        //}

        public void FillTreeView(TreeView trvDebug, ScriptFunction script)
        {
            TreeNode tn = new TreeNode(script.GetName());
            FillTreeNode(SyntaxDatums[script.SyntaxChunkNumber].Child, tn);
            trvDebug.Nodes.Add(tn);
            trvDebug.ExpandAll();
        }

        private void FillTreeNode(ScriptingSyntaxDatum datum, TreeNode tn)
        {
            //if (datum.HasChild())
            //{
            //    TreeNode childNode = new TreeNode(datum.Child.GetValue().ToString());
            //    childNode.Checked = datum.Child.Keep;
            //    tn.Nodes.Add(childNode);
            //    FillTreeNode(datum.Child, childNode, tn);
            //}

            if (datum.HasSibling())
            {
                TreeNode siblingNode = new TreeNode(GetDatumCode(datum.Sibling.Child, datum));
                siblingNode.ToolTipText = string.Empty;
                siblingNode.Checked = datum.Sibling.Keep;
                siblingNode.Tag = datum.Sibling;
                tn.Nodes.Add(siblingNode);
                FillTreeNode(datum.Sibling, tn);
            }
        }
    }
}
